package repo

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"golang.org/x/crypto/bcrypt"
	"github.com/redis/go-redis/v9"
	"github.com/star-find-cloud/star-chat/pkg/database"
	log "github.com/star-find-cloud/star-chat/pkg/logger"
	"github.com/star-find-cloud/star-chat/user/domain"
	"github.com/star-find-cloud/star-chat/utils"
)

type UserRepoImpl struct {
	pgDB    database.SQLDatabase
	mongoDB database.NoSQLDatabase
	cache   *database.Redis
}

func NewUserRepo(pgDB database.SQLDatabase, mongoDB database.NoSQLDatabase, cache *database.Redis) *UserRepoImpl {
	return &UserRepoImpl{
		pgDB:    pgDB,
		mongoDB: mongoDB,
		cache:   cache,
	}
}

func (r *UserRepoImpl) Create(ctx context.Context, user *domain.User) (int64, error) {
	sqlStr := "insert into business.user(name, nickname, password, email, country_code, phone, sex, tags, status, last_ip, vip_status, create_time) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,now()) RETURNING id;"

	var userID int64
	err := r.pgDB.GetDB().GetContext(ctx, &userID, sqlStr,
		user.Name,
		user.NickName,
		user.Password,
		user.Email,
		user.CountryCode,
		user.Phone,
		user.Sex,
		user.Tags,
		user.Status,
		user.LastIP,
		user.VipStatus,
	)

	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database insert user metadata error: %v", err)
			return 0, err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return 0, err
	}

	return userID, nil
}

func (r *UserRepoImpl) GetAll(ctx context.Context, id int64) (*domain.User, error) {
	var (
		sqlStr = "select * from business.user where id = $1;"
		dbUser = &userDB{}
	)

	var err = r.pgDB.GetDB().GetContext(ctx, dbUser, sqlStr, id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database get user metadata error: %v", err)
			return nil, err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return nil, err
	}

	return toDomainUser(dbUser), nil

}

func (r *UserRepoImpl) GetByID(ctx context.Context, id int64) (*domain.User, error) {
	var (
		sqlStr = "select id, name, nickname, email, country_code, phone, sex, tags, status, last_ip, vip_status, vip_expired_time from business.user where id = $1;"
		dbUser = &userDB{}
	)

	var err = r.pgDB.GetDB().GetContext(ctx, dbUser, sqlStr, id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database get user metadata error: %v", err)
			return nil, err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return nil, err
	}

	return toDomainUser(dbUser), nil
}

func (r *UserRepoImpl) GetByEmail(ctx context.Context, email string) (*domain.User, error) {
	var (
		sqlStr = "select id, name, nickname, email, country_code, phone, sex, tags, status, last_ip, vip_status, vip_expired_time from business.user where email = $1;"
		dbUser = &userDB{}
	)

	var err = r.pgDB.GetDB().GetContext(ctx, dbUser, sqlStr, email)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database get user metadata error: %v", err)
			return nil, err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return nil, err
	}

	return toDomainUser(dbUser), nil
}

func (r *UserRepoImpl) GetByPhone(ctx context.Context, countryCode, phone int64) (*domain.User, error) {
	var (
		sqlStr = "select id, name, nickname, email, country_code, phone, sex, tags, status, last_ip, vip_status, vip_expired_time from business.user where phone = $1 and country_code = $2;"
		dbUser = &userDB{}
	)

	var err = r.pgDB.GetDB().GetContext(ctx, dbUser, sqlStr, phone, countryCode)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database get user metadata error: %v", err)
			return nil, err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return nil, err
	}

	return toDomainUser(dbUser), nil
}

func (r *UserRepoImpl) CheckPassword(ctx context.Context, id int64, password string) (bool, error) {
	var sqlStr = "select password from business.user where id=$1;"

	var hashedPassword string
	err := r.pgDB.GetDB().GetContext(ctx, &hashedPassword, sqlStr, id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database get user metadata error: %v", err)
			return false, err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return false, err
	}
	
	// 使用bcrypt验证密码
	err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
	return err == nil, nil
}

func (r *UserRepoImpl) Update(ctx context.Context, user *domain.User) error {
	var sqlStr = "update business.user set name=$1, nickname=$2, email=$3, country_code=$4,phone=$5,sex=$6,tags=$7,status=$8,last_ip=$9,vip_status=$10,vip_expired_time=$11 , update_time = now() where id = $12;"

	_, err := r.pgDB.GetDB().ExecContext(ctx, sqlStr,
		user.Name,
		user.NickName,
		user.Email,
		user.CountryCode,
		user.Phone,
		user.Sex,
		user.Tags,
		user.Status,
		user.LastIP,
		user.VipStatus,
		user.VipExpiredTime,
		user.ID,
	)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database update user metadata error: %v", err)
			return err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return err
	}
	return nil
}

func (r *UserRepoImpl) UpdatePassword(ctx context.Context, id int64, newPassword string) error {
	var sqlStr = "update business.user set password = $1 , update_time = now() where id = $2;"

	_, err := r.pgDB.GetDB().ExecContext(ctx, sqlStr, newPassword, id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database update user metadata error: %v", err)
			return err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return err
	}
	return nil
}

func (r *UserRepoImpl) UpdatePhone(ctx context.Context, id, countryCode, Phone int64) error {
	var sqlStr = "update business.user set country_code=$1, phone=$2, update_time = now() where id=$3;"

	_, err := r.pgDB.GetDB().ExecContext(ctx, sqlStr, countryCode, Phone, id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database update user metadata error: %v", err)
			return err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return err
	}
	return nil
}

func (r *UserRepoImpl) UpdateAvatar(ctx context.Context, id, avatarID int64) error {
	var sqlStr = "update business.user set avatar = $1 , update_time = now() where id = $2;"

	_, err := r.pgDB.GetDB().ExecContext(ctx, sqlStr, avatarID, id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database update user avatar error: %v", err)
			return err
		}
		log.AppLogger.Errorf("an error occurred in the user repo when updating avatar, err: %v", err)
		return err
	}
	return nil
}

func (r *UserRepoImpl) UpdateStatus(ctx context.Context, id, status int64) error {
	var sqlStr = "update business.user set status = $1 , update_time = now() where id = $2;"

	_, err := r.pgDB.GetDB().ExecContext(ctx, sqlStr, status, id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database update user status error: %v", err)
			return err
		}
		log.AppLogger.Errorf("an error occurred in the user repo when updating status, err: %v", err)
		return err
	}
	return nil
}

func (r *UserRepoImpl) Delete(ctx context.Context, id int64) error {
	var sqlStr = "update business.user set status = $1, delete_time = now() where id = $2;"

	_, err := r.pgDB.GetDB().ExecContext(ctx, sqlStr, domain.Deleted, id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			log.PostgreSQLLogger.Errorf("Database delete user metadata error: %v", err)
			return err
		}
		log.AppLogger.Errorf("an error occurred in the user repo, err: %v", err)
		return err
	}
	return nil
}

func (r *UserRepoImpl) IsExistsByID(ctx context.Context, id int64) (bool, error) {
	var sqlStr = "select count(*) from business.user where id = $1 and status != $2;"
	
	var count int
	err := r.pgDB.GetDB().GetContext(ctx, &count, sqlStr, id, domain.Deleted)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return false, nil
		}
		log.AppLogger.Errorf("an error occurred in the user repo when checking user existence by ID, err: %v", err)
		return false, err
	}
	return count > 0, nil
}

func (r *UserRepoImpl) IsExistsByPhone(ctx context.Context, countryCode, phone int64) (bool, error) {
	var sqlStr = "select count(*) from business.user where country_code = $1 and phone = $2 and status != $3;"
	
	var count int
	err := r.pgDB.GetDB().GetContext(ctx, &count, sqlStr, countryCode, phone, domain.Deleted)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return false, nil
		}
		log.AppLogger.Errorf("an error occurred in the user repo when checking user existence by phone, err: %v", err)
		return false, err
	}
	return count > 0, nil
}

func (r *UserRepoImpl) IsExistsByEmail(ctx context.Context, email string) (bool, error) {
	var sqlStr = "select count(*) from business.user where email = $1 and status != $2;"
	
	var count int
	err := r.pgDB.GetDB().GetContext(ctx, &count, sqlStr, email, domain.Deleted)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return false, nil
		}
		log.AppLogger.Errorf("an error occurred in the user repo when checking user existence by email, err: %v", err)
		return false, err
	}
	return count > 0, nil
}

func (r *UserRepoImpl) CheckEmailVerificationCode(ctx context.Context, email string, verificationCode string) (bool, error) {
	// 从Redis缓存中获取存储的验证码
	storedCode, err := r.cache.GetCache().Get(ctx, email).Result()
	if err != nil {
		if errors.Is(err, redis.Nil) {
			log.RedisLogger.Warnf("email:%s err: %v", email, err)
			return false, err
		}
		log.AppLogger.Errorf("获取验证码失败: %v", err)
		return false, fmt.Errorf("获取验证码失败: %w", err)
	}

	// 比较验证码是否匹配
	isValid, err := utils.ValidateCode(verificationCode, storedCode)
	if err != nil {
		log.AppLogger.Errorln(err)
		return false, err
	}

	// 如果验证成功，删除验证码防止重复使用
	if isValid {
		_, err = r.cache.GetCache().Del(ctx, email).Result()
		if err != nil {
			log.AppLogger.Warnf("删除已使用的验证码失败: %v", err)
		}
	}

	return isValid, nil
}
